using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using XnaMD2;
using PK3Model;
using OneLine.Components;

namespace OneLine
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class OneLineGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont menuFont;

        List<CPK3Model> models = new List<CPK3Model>();
        int model_selected = 0;

        Texture2D modelTexture;

        CameraComponent camera;
        Map map;
        Level level;
        Wave wave;
        Player player;

        Pathfinder pathFinder;

        BasicEffect modelEffect;
        BasicEffect levelEffect;

        int windowWidth;
        int windowHeight;

        Input input;
        PieMenu menu;
        PieMenuNode defaultMenu;

        Model fence;


        public OneLineGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            camera = new CameraComponent(this);

            //camera = new Camera(this, new Vector3(map.Width / 2, 5f, map.Height + 1), new Vector3(map.Width / 2, 0, 0), Vector3.Up);
            Components.Add(camera);

            input = new Input();
            menu = new PieMenu();

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            // Initialize camera
            map = new Map();

            graphics.SynchronizeWithVerticalRetrace = false;
            windowWidth = GraphicsDevice.DisplayMode.Width / 2;
            windowHeight = GraphicsDevice.DisplayMode.Height / 2;
            graphics.PreferredBackBufferWidth = windowWidth;
            graphics.PreferredBackBufferHeight = windowHeight;
            graphics.PreferMultiSampling = true;
            graphics.ApplyChanges();

            pathFinder = new Pathfinder(map);

            level = new Level(this, map);
            Components.Add(level);

            InitCamera();

            base.Initialize();
        }

        private void InitCamera()
        {
            GraphicsDevice device = graphics.GraphicsDevice;
            float aspectRatio = (float)windowWidth / (float)windowHeight;

            camera.Perspective(90.0F, aspectRatio, 0.01F, 3000.0F);
            camera.Position = new Vector3(level.Width / 2f, 2.5f, level.Height);
            camera.Acceleration = new Vector3(4.0f, 4.0f, 4.0f);
            camera.Velocity = new Vector3(1.0f, 1.0f, 1.0f);
            camera.OrbitMinZoom = 5.5f;
            camera.OrbitMaxZoom = 500.0f;


            camera.OrbitOffsetDistance = camera.OrbitMinZoom;
            camera.Rotate(0.0f, 0.0f, 0.0f);
            camera.CurrentBehavior = Camera.Behavior.Spectator;

        }

        private void PerformCameraCollisionDetection()
        {

            Vector3 newPos = camera.Position;


            if (camera.Position.Y < 0.5F)
                newPos.Y = 0.5F;


            camera.Position = newPos;

        }

        #region CalculateCursorRay

        // CalculateCursorRay Calculates a world space ray starting at the camera's
        // "eye" and pointing in the direction of the cursor. Viewport.Unproject is used
        // to accomplish this. see the accompanying documentation for more explanation
        // of the math behind this function.
        public Ray CalculateCursorRay(Matrix projectionMatrix, Matrix viewMatrix)
        {
            // create 2 positions in screenspace using the cursor position. 0 is as
            // close as possible to the camera, 1 is as far away as possible.
            Vector3 nearSource = new Vector3(camera.MousePosition, 0f);
            Vector3 farSource = new Vector3(camera.MousePosition, 1f);

            // use Viewport.Unproject to tell what those two screen space positions
            // would be in world space. we'll need the projection matrix and view
            // matrix, which we have saved as member variables. We also need a world
            // matrix, which can just be identity.
            Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource,
                projectionMatrix, viewMatrix, Matrix.Identity);

            Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource,
                projectionMatrix, viewMatrix, Matrix.Identity);

            // find the direction vector that goes from the nearPoint to the farPoint
            // and normalize it....
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();

            // and then create a new ray using nearPoint as the source.
            return new Ray(nearPoint, direction);
        }

        #endregion

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            menuFont = Content.Load<SpriteFont>("Fonts/Segoe UI Mono");

            // TODO: use this.Content to load your game content here
            models.Add(Content.Load<CPK3Model>("md3-brawl"));
            //models.Add(Content.Load<CPK3Model>("md3-lara"));
            models[0].initialize(GraphicsDevice);
            models[0].setAnimation("TORSO_STAND");
            models[0].setAnimation("LEGS_RUN");

            /*models[1].initialize(GraphicsDevice);
            models[1].setAnimation("TORSO_STAND");
            models[1].setAnimation("LEGS_IDLE");
            */
            wave = new Wave(0, 10, pathFinder, level, models[0]);
            wave.Start();

            player = new Player();
            //player.Start(map, Content);

            levelEffect = new BasicEffect(GraphicsDevice);
            levelEffect.TextureEnabled = false;
            levelEffect.VertexColorEnabled = true;

            defaultMenu = new PieMenuNode();
            defaultMenu.Add(new PieMenuNode("Add Tower", Content.Load<Texture2D>("wall_tower_guard"), AddTower1));
            defaultMenu.Add(new PieMenuNode("Add Tower", Content.Load<Texture2D>("wall_tower_guard"), AddTower2));
            defaultMenu.Add(new PieMenuNode("Add Tower", Content.Load<Texture2D>("wall_tower_guard"), AddTower3));

            fence = Content.Load<Model>("fence1");
            
        }

        private void AddTower1(object sender)
        {
            player.AddTower(selectedCell.X, selectedCell.Y, GraphicsDevice, Content, "md3-lara");
        }

        private void UpdateMap()
        {
            map.AddTower(selectedCell.X, selectedCell.Y);
            pathFinder.Update(map);
        }

        private void AddTower2(object sender)
        {
            player.AddTower(selectedCell.X, selectedCell.Y, GraphicsDevice, Content, "md3-homer");
            UpdateMap();
        }

        private void AddTower3(object sender)
        {
            player.AddTower(selectedCell.X, selectedCell.Y, GraphicsDevice, Content, "md3-conni");
            UpdateMap();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }


        bool jumping = false;
        KeyboardState prev_state_key;

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();
            PerformCameraCollisionDetection();
            // TODO: Add your update logic here
            wave.Update(gameTime);

            player.Update(wave, gameTime);

            input.Update();
            menu.Update(gameTime);
            menu.HandleInput(input, menu.Position - input.CursorPosition);

            CheckForSelection();


            KeyboardState key = Keyboard.GetState();
            if (key.IsKeyDown(Keys.Right) && !prev_state_key.IsKeyDown(Keys.Right))
                ++model_selected;
            else if (key.IsKeyDown(Keys.Left) && !prev_state_key.IsKeyDown(Keys.Left))
                --model_selected;

            if (model_selected < 0)
                model_selected = models.Count - 1;
            else if (model_selected >= models.Count)
                model_selected = 0;

            CPK3Model m = models[model_selected];
            if (key.IsKeyDown(Keys.F1))
            {
                m.setAnimation("TORSO_STAND");
                m.setAnimation("LEGS_RUN");
            }
            else if (key.IsKeyDown(Keys.F2))
            {
                m.setAnimation("TORSO_GESTURE");
                m.setAnimation("LEGS_IDLE");
            }
            else if (key.IsKeyDown(Keys.F3))
            {
                m.setAnimation("TORSO_ATTACK");
                m.setAnimation("LEGS_JUMP");
                jumping = true;
            }
            else if (key.IsKeyDown(Keys.F4))
            {
                m.setAnimation("BOTH_DEATH1");
            }
            else if (key.IsKeyDown(Keys.F5))
            {
                m.setAnimation("BOTH_DEATH3");
            }

            if (key.IsKeyDown(Keys.F9))
            {
                m.currentHeadID = 0;
                m.currentLegsID = 0;
                m.currentUpperID = 0;
            }
            else if (key.IsKeyDown(Keys.F10))
            {
                m.currentHeadID = 1;
                m.currentLegsID = 1;
                m.currentUpperID = 1;
            }
            else if (key.IsKeyDown(Keys.F11))
            {
                m.currentHeadID = 2;
                m.currentLegsID = 2;
                m.currentUpperID = 2;
            }

            if (jumping)
            {
                if (m.isFinishAnimationLegs())
                {
                    m.setAnimation("LEGS_RUN");
                    jumping = false;
                }
            }

            prev_state_key = key;

            foreach (CPK3Model model in this.models)
                model.update(gameTime);

            base.Update(gameTime);
        }

        int lastSelected = -1;
        Point selectedCell;
        private void CheckForSelection()
        {
            if (input.InGameSelect)
            {
                if (lastSelected > 0)
                {
                    for (int j = lastSelected; j < lastSelected + 6; j++)
                        level.Verts[j].Color = Color.Green;
                    lastSelected = -1;
                }

                Ray ray = CalculateCursorRay(camera.ProjectionMatrix, camera.ViewMatrix);
                float? point = ray.Intersects(new Plane(Vector3.Up, 0));
                if (point.HasValue)
                {
                    Vector3 pos = ray.Position + ray.Direction * point.Value;
                    if (pos.X >= 0 && pos.X <= level.Width &&
                        pos.Z >= 0 && pos.Z <= level.Height)
                    {
                        selectedCell.X = (int)pos.X;
                        selectedCell.Y = (int)pos.Z;

                        int textureIndex = map.GetIndex(selectedCell.X, selectedCell.Y);
                        if (textureIndex == 1)
                        {
                            menu.Show(defaultMenu, input.CursorPosition);
                        }
                        else if (textureIndex == 2)
                        {
                            //menu.Show(upgradeMenu, input.CursorPosition);
                        }

                        if(textureIndex > 0)
                        {
                            int i = (selectedCell.Y + selectedCell.X * level.Width) * 6;

                            for (int j = i; j < i + 6; j++)
                                level.Verts[j].Color = Color.ForestGreen;

                            lastSelected = i;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            //GraphicsDevice.RasterizerState = new RasterizerState() { FillMode = Microsoft.Xna.Framework.Graphics.FillMode.WireFrame };

            // TODO: Add your drawing code here

            
            //Matrix world = Matrix.CreateFromQuaternion(Quaternion.Inverse(camera.Orientation)) * new Matrix();


            //world.
            //world.M41 = camera.Position.X ;
            //world.M42 = camera.Position.Y ;
            //world.M43 = camera.Position.Z ;
            //levelEffect.World = world;
            levelEffect.View = camera.ViewMatrix;
            levelEffect.Projection = camera.ProjectionMatrix;

            foreach (var pass in levelEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, level.Verts, 0, level.Verts.Length / 3);
            }

            foreach (var pass in levelEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                fence.Draw(Matrix.CreateScale(0.0005f), camera.ViewMatrix, camera.ViewProjectionMatrix);
            }

            menu.Draw(spriteBatch, menuFont);

            wave.Draw(GraphicsDevice, camera.ViewMatrix, camera.ProjectionMatrix);

            player.Draw(GraphicsDevice, camera.ViewMatrix, camera.ProjectionMatrix);

            base.Draw(gameTime);
        }
    }
}
